|








| |
在VB程序中处理随机事件
济南同创系统工程中心 于琬玲
在Visual Basic(以下简称VB)程序设计过程中,如何轻松地处理众多的随机事件,往往是制作大型系统首先要考虑的问题之一。例如,多个窗口同时打开同一个表(Table),一个窗口中对数据进行了修改,而其它窗口也能够随之进行数据更新,这时就需要有一条说明数据改变了的消息在所有窗口间进行广播。在C中,只需要定义一条用户消息即可实现这一点;而用VB编程就不那么简单。
早期实现方法及局限性
- 对于以上问题,笔者早期的实现方法是,自定义一个消息结构(VbMsg),并在程序的主窗体内建立一个消息广播引擎,主要由一个消息队列和一个定时消息广播器所组成。消息广播器每隔一固定时间检查一次消息队列,如果有消息存在,就将其发送给所有打开的窗口,并将该消息从队列中删除。如此再定义一个全局的消息发送过程(SendMsg),将要发送的消息(VbMsg)送入消息队列。这样当需要广播消息时,只需填充好消息结构,调用SendMsg过程即可。
-
- 这里较为复杂的是消息广播器如何将消息发送到各窗口。这需要作个硬性规定,即每一个窗体都必须定义一个形式完全相同的消息接收函数(RecMsg),在这个函数中对接收到的消息进行处理,当然也可以什么都不做。有了这样的规定之后,消息广播器在进行广播时,就可以利用VB系统定义的全局变量Forms,遍历所有的窗体,并调用一遍每个窗体的消息接收函数。其主要代码如下:
-
- Public Sub SendMsgToForms(msg as VbMsg)
- Dim frm as Form
- For Each frm In Forms
- frm.RecMsg msg
- Next frm
- End Sub
-
- 通过上面的这些过程,就可以实现在独立的程序中,对随机事件进行异步处理。这一方法的效果基本令人满意。但是它有几个较大的局限性:
- 定时检查消息队列需要利用Timer控件进行触发,这在程序运行时,就必然要牺牲一部分效率;
- 消息广播的范围限定在一个程序模块内,如果整个系统分成多个大的模块,那么存在于动态链接模块(.DLL)中的窗体将不能直接接收到广播消息,更无法实现进程间的消息传递;
- 消息的接收者只能是窗体,而作为真正的基础单元“类”却无法直接接收消息。
-
VB5.0的新特性及实现方法
- VB 5.0 企业版增添了许多强有力的特性,可以解决以上难题。
-
- 1、 用户自定义事件 在类模块中,可以使用Event关键字来定义用户自定义事件,使用
RaiseEvent
语句来产生该事件,这一机制给处理随机事件带来了很大方便。本文中的消息广播引擎,就可以不再使用Timer控件做支持,而是当收到需要广播的消息时,产生一个预定义的事件;而需要处理消息的客体对象,只需截获该事件。
- 2、 ActiveX EXE组件 利用VB,可以方便地将共享代码封装在ActiveX组件之中,从而可以实现跨进程间的消息传递。因为ActiveX组件有内部(DLL)、外部(EXE)两种,外部组件可以对模块内的全局数据实现共享。
- 3、 远程自动化连接 ActiveX组件是一种标准的客户机/服务器结构,利用Windows平台的COM模型,VB能够方便地将这种结构扩展到整个网络的范围。所以,消息广播设计既可实现进程间的消息传递,也可实现网络上的消息传递。
-
- 根据以上思想,笔者通过四个模块之间的相互协作,完成了消息的发送、广播及接收,并将这四个模块封装在一个ActiveX
EXE组件之中。下面就是这四个类模块的简单介绍及源代码。
-
- 类模块之一:Msg.cls
- 该模块定义了消息数据结构VbMsg类,它是消息传递中的载体。这里只是一个简单的例子,如果想实现更多的功能,如建立两点间的数据通道,而不是单纯的广播消息,则要对该结构进行一些扩充。
- BEGIN
- MultiUse = -1 'True
- END
- Attribute VB_Name = “VbMsg"
- Attribute VB_GlobalNameSpace = False
- Attribute VB_Creatable = True
- Attribute VB_PredeclaredId = False
- Attribute VB_Exposed = True
- Option Explicit
- '消息类:定义全局的消息结构
- Public iType As Long '消息类型编号
- Public iName As String '消息名
- Public iSource As String '消息源说明
- Public iDescription As String '消息说明
- Dim iT As Date '消息发生时间
- '返回日期型时间
- Public Property Get iTime() As Date
- iTime = iT
- End Property
- '返回字符型时间
- Public Property Get iTimeStr() As String
- iTimeStr = Format(iT, “yyyy.mm.dd hh:mm:ss")
- End Property
- '在对象被建立时,设置消息发生时间
- Private Sub Class_Initialize()
- iT = Now()
- End Sub
-
- 类模块之二:MsgCli.cls
- 本模块是对客户接收端MsgClient类的定义,这相当于一个消息接收器。在这个类中定义的一个RecMsg事件,当接收器收到消息时(过程SetMsg被调用),就产生这一事件;接收器的建立者截获这一事件,并处理消息。为了避免接收不必要的消息,声明了minMsg、maxMsg两个变量,以便对VbMsg中的iType属性进行过滤。
- BEGIN
- MultiUse = -1 'True
- END
- Attribute VB_Name = “MsgClient"
- Attribute VB_GlobalNameSpace = False
- Attribute VB_Creatable = True
- Attribute VB_PredeclaredId = False
- Attribute VB_Exposed = True
- Option Explicit
- ' 客户消息接收类
- '定义接收消息事件,该对象的宿主类应截获该事件,并处
- 理接收到的消息
- Public Event RecMsg(ByVal msg As VbMsg)
- '通过设置消息的接收范围,过滤掉不需要的消息
- Public minMsg As Long
- Public maxMsg As Long
-
- '该对象的标志编号,使用时不应修改该值
- Public ID As Long
- '事件产生过程,只应由消息服务器(MsgServer)调用
- Public Sub SetMsg(msg As VbMsg)
- If msg.iType >= minMsg And msg.iType <= maxMsg Then
- RaiseEvent RecMsg(msg)
- End If
- End Sub
- '根据ID返回对象的关键字,只应由消息服务器调用
- Public Property Get Key() As String
- Key = “ID:" & ID
- End Property
-
- 类模块之三:Global.bas
- 本模块声明了两个全局变量,一个是接收器(MsgClient)列表(Clients),一个是接收器计数器,为每个接收器分配一个唯一的ID标志。把变量放在单独的模块中,是为了实现数据在进程间的共享,是跨进程间消息传递的关键所在(应保证在编译时工程是单线程的,否则不能实现数据共享)。
- Attribute VB_Name = “modGlobal"
- Option Explicit
- '消息服务器全局变量,消息接收客户列表
- Public Clients As New Collection
- '消息接收客户ID计数器
- Public CliCount As Long
- 类模块之四:MsgSrv.cls
- 本模块定义了消息服务器类MsgServer,该类是消息广播引擎的主体,它主要管理维护消息接收器列表(Clients),将发送来的消息(调用SendMsg过程)依次发送给列表中的所有接收器。请注意:为了方便使用,该类被声明为公共全局类。
- BEGIN
- MultiUse = -1 'True
- END
- Attribute VB_Name = “MsgServer"
- Attribute VB_GlobalNameSpace = True
- Attribute VB_Creatable = True
- Attribute VB_PredeclaredId = False
- Attribute VB_Exposed = True
- Option Explicit
-
- '消息服务器类,发送消息
- Public Sub SendMsg(msg As VbMsg)
- Dim c As MsgClient
- For Each c In Clients
- c.SetMsg msg
- DoEvents
- Next c
- End Sub
- '增删消息接收客户
- Public Sub AddMsgClient(c As MsgClient)
- CliCount = CliCount + 1
- c.Id = CliCount
- Clients.Add c, c.Key
- End Sub
- Public Sub DelMsgClient(c As MsgClient)
- Clients.Remove c.Key
- If Clients.Count = 0 Then CliCount = 0
- End Sub
-
应用举例
- 至此,一个小巧灵活的消息广播引擎就完成了,它的使用范围很广,用起来也很方便,只需在工程中引入编译过的ActiveX组件,就可以直接调用SendMsg发送消息。但可能在安装消息接收器(MsgClient)时会稍许有点麻烦,下面举例说明其应用。
- 在设计Windows程序时,往往希望调试时能看到程序运行时后台的一些情况。利用VB的单步执行或Debug命令,会受到一些限制。利用消息广播引擎,制作一个通用的实时消息事件查看程序,就可以很好地解决这一问题。查看程序的主要工作是捕捉一组事先定义好的消息事件,并将消息的内容显示在列表框内。本应用可以只用一个窗体完成,主要代码如下。
-
- Const MsgInfoID=101
- Private WithEvents mClient As MsgClient
- Private Sub Form_Load()
- Set mClient = New MsgClient
- MClient.minMsg= MsgInfoID
- MClient.maxMsg= MsgInfoID
- AddMsgClient mClient
- End Sub
- Private Sub Form_Unload(Cancel As Integer)
- DelMsgClient mClient
- End Sub
- Private Sub mClient _RecMsg(ByVal msg As VbMsgSrv.VbMsg)
- List1.AddItem msg.iTimeStr & Chr(9) & msg.iName & Chr(9) & msg.iDescription
- End Sub
- 在被调试的程序中,为了调用方便,可以编写一个全局过程:
- Const MsgInfoID=101
- Public Sub MsgInfo(iName As String,iDes As String)
- Dim msg As New MsgClient
- With msg
- .iName = iName
- . iDescription = iDes
- End With
- SendMsg msg
- End Sub
-
- 在程序的重点环节插入MsgInfo过程,运行时有关信息就会在事件查看程序的窗口中显示出来。这种方法尤其适合调试多程序协作的软件系统。当软件系统正式交给用户时,插入的MsgInfo过程也不一定要全部删掉,只要将实时查看改为写入日志文件,作为日后软件维护的资料。
计算机世界
|